﻿/*********************************************************************************
**                                                                              **
**  Copyright (C) 2024 LiLong                                                   **
**  This file is part of RinexFileReader.                                       **
**                                                                              **
**  RinexFileReader is free software: you can redistribute it and/or modify     **
**  it under the terms of the GNU General Public License as published by        **
**  the Free Software Foundation, either version 3 of the License, or           **
**  (at your option) any later version.                                         **
**                                                                              **
**  RinexFileReader is distributed in the hope that it will be useful,          **
**  but WITHOUT ANY WARRANTY; without even the implied warranty of              **
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               **
**  GNU General Public License for more details.                                **
**                                                                              **
**  You should have received a copy of the GNU General Public License           **
**  along with RinexFileReader. If not, see <https://www.gnu.org/licenses/>.    **
**********************************************************************************/
#pragma once

#include "CommonPEG.h"

#include <ranges>

namespace Rinex
{
struct NavigationMessageData
{
    // IONEX VERSION / TYPE
    double version;
    SatelliteSystem system;
    // PGM / RUN BY / DATE
    std::string program;
    std::string agency;
    std::string createDateTime;
    // *REC # / TYPE / VERS
    std::optional<std::string> recNumber;
    std::optional<std::string> recType;
    std::optional<std::string> recVersion;
    // *COMMENT
    std::vector<std::string> comments;
    // *IONOSPHERIC CORR
    std::vector<IonosphericCorr> ionosphericCorrs;
    // *TIME SYSTEM CORR
    std::vector<TimeSystemCorr> timeSystemCorrs;
    // *MERGED FILE
    std::optional<int> mergedFile;
    // *DOI
    std::optional<std::string> doi;
    // *LICENSE OF USE
    std::optional<std::string> licenseOfUse;
    // *STATION INFORMATION
    std::optional<std::string> stationInformation;
    // LEAP SECONDS
    LeapSeconds leapSeconds;
    ////////////////////////////////////////////////////
    std::vector<NMData> datas;
};
namespace PEG
{
namespace NavigationMessage
{
using namespace tao::pegtl;
// clang-format off
struct Version : Float {};
struct Type : one<'N'> {};
struct Satellite : one<'G', 'R', 'E', 'J', 'C', 'I', 'S', 'M'> {};

struct RINEX_VERSION_TYPE : seq<Space, Version, rep<11, ascii::any>, Type, rep<19, ascii::any>, Satellite, rep<19, ascii::any>, RINEX_KEYWORDS("RINEX VERSION / TYPE")> {};
struct PGM_RUNBY_DATE : seq<rep<60, ascii::any>, RINEX_KEYWORDS("PGM / RUN BY / DATE")> {};
struct COMMENT : seq<rep<60, ascii::any>, RINEX_KEYWORDS("COMMENT")> {};
struct IONOSPHERIC_CORR : seq<rep<60, ascii::any>, RINEX_KEYWORDS("IONOSPHERIC CORR")> {};
struct TIME_SYSTEM_CORR : seq<rep<60, ascii::any>, RINEX_KEYWORDS("TIME SYSTEM CORR")> {};
struct REC_TYPE_VERS : seq<rep<60, ascii::any>, RINEX_KEYWORDS("REC # / TYPE / VERS")> {};
struct MERGED_FILE : seq<rep<60, ascii::any>, RINEX_KEYWORDS("MERGED FILE")> {};
struct DOI : seq<rep<60, ascii::any>, RINEX_KEYWORDS("DOI")> {};
struct LICENSE_OF_USE : seq<rep<60, ascii::any>, RINEX_KEYWORDS("LICENSE OF USE")> {};
struct STATION_INFORMATION : seq<rep<60, ascii::any>, RINEX_KEYWORDS("STATION INFORMATION")> {};
struct LEAP_SECONDS : seq<rep<60, ascii::any>, RINEX_KEYWORDS("LEAP SECONDS")> {};
struct HeaderEnd : until<RINEX_KEYWORDS("END OF HEADER")> {};

// clang-format on

struct HeaderLine
    : sor<PGM_RUNBY_DATE,
          COMMENT,
          IONOSPHERIC_CORR,
          TIME_SYSTEM_CORR,
          REC_TYPE_VERS,
          MERGED_FILE,
          DOI,
          LICENSE_OF_USE,
          STATION_INFORMATION,
          LEAP_SECONDS>
{
};

struct Header : seq<RINEX_VERSION_TYPE, plus<HeaderLine>, until<HeaderEnd>>
{
};

struct NM_DATA
{
};
using Rule = seq<Header, plus<NM_DATA>, eof>;

template<typename T>
struct Action
{
};

template<>
struct Action<Version> : DoubleHelper<struct Action<Version>>
{
    static void setValue(NavigationMessageData& data, double value) { data.version = value; }
};

template<>
struct Action<Satellite>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, NavigationMessageData& data)
    {
        data.system = toSatelliteSystem(std::string_view(input.begin(), input.end()));
    }
};

template<>
struct Action<PGM_RUNBY_DATE>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, NavigationMessageData& data)
    {
        std::string_view str(input.begin(), input.end());
        data.program        = trim(str.substr(0, 20));
        data.agency         = trim(str.substr(20, 20));
        data.createDateTime = trim(str.substr(40, 20));
    }
};

template<>
struct Action<COMMENT>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, NavigationMessageData& data)
    {
        data.comments.push_back(std::string(trim(std::string_view(input.begin(), input.begin() + 60))));
    }
};

template<>
struct Action<REC_TYPE_VERS>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, NavigationMessageData& data)
    {
        std::string_view str(input.begin(), input.end());
        data.recNumber  = trim(str.substr(0, 20));  // A20
        data.recType    = trim(str.substr(20, 20)); // A20
        data.recVersion = trim(str.substr(40, 20)); // A20
    }
};

inline CorrectionType toCorrectionType(std::string_view str)
{
    constexpr static auto list = { "GAL", "GPSA", "GPSB", "QZSA", "QZSB", "BDSA", "BDSB", "IRNA", "IRNB" };
    auto it                    = std::ranges::find(list, str);
    if (it == list.end())
        throw std::runtime_error("Unknown IonosphericCorr");
    return static_cast<CorrectionType>(std::distance(list.begin(), it));
}

template<>
struct Action<IONOSPHERIC_CORR>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, NavigationMessageData& data)
    {
        std::string_view str(input.begin(), input.end());
        IonosphericCorr ic;
        ic.correctionType = toCorrectionType(trim(str.substr(0, 5)));                              // A4, 1X
        for (auto view : str | std::views::drop(5) | std::views::take(48) | std::views::chunk(12)) // 4D12.4
        {
            auto s = trim(std::string_view(view.begin(), view.end()));
            if (!s.empty())
                ic.parameters.push_back(stod(s));
        }
        if (str[54] != ' ')
            ic.timeMark = str[54]; // 1X, A1
        auto sv = trim(str.substr(56, 2));
        if (!sv.empty())
            ic.svID = stoi(sv); // 1X, I2
        data.ionosphericCorrs.push_back(ic);
    }
};

inline TimeSystemCorrType toTimeSystemCorrType(std::string_view str)
{
    constexpr static auto list = { "GPUT", "GLUT", "GAUT", "BDUT", "QZUT", "IRUT", "SBUT", "GLGP", "GAGP", "QZGP", "IRGP" };
    auto it                    = std::ranges::find(list, str);
    if (it == list.end())
        throw std::runtime_error("Unknown TimeSystemCorrType");
    return static_cast<TimeSystemCorrType>(std::distance(list.begin(), it));
}

template<>
struct Action<TIME_SYSTEM_CORR>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, NavigationMessageData& data)
    {
        std::string_view str(input.begin(), input.end());
        TimeSystemCorr tsc;
        tsc.type = toTimeSystemCorrType(trim(str.substr(0, 4))); // A4, 1X
        tsc.a0   = stod(trim(str.substr(5, 17)));                // D17.10
        tsc.a1   = stod(trim(str.substr(22, 16)));               // D16.9
        tsc.t    = stoi(trim(str.substr(39, 6)));                // 1X, I6
        tsc.w    = stoi(trim(str.substr(46, 4)));                // 1X, I4
        auto si  = trim(str.substr(51, 5));                      // 1X, A5, 1X
        if (!si.empty())
            tsc.satelliteID = si;
        auto utc = trim(str.substr(57, 2)); // I2, 1X
        if (!utc.empty())
            tsc.utcIdentifier = stoi(utc);
        data.timeSystemCorrs.push_back(tsc);
    }
};

template<>
struct Action<MERGED_FILE>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, NavigationMessageData& data)
    {
        data.mergedFile = stoi(trim(std::string_view(input.begin(), input.begin() + 9))); // I9
    }
};

template<>
struct Action<DOI>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, NavigationMessageData& data)
    {
        data.doi = trim(std::string_view(input.begin(), input.end()));
    }
};

template<>
struct Action<LICENSE_OF_USE>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, NavigationMessageData& data)
    {
        data.licenseOfUse = trim(std::string_view(input.begin(), input.end()));
    }
};

template<>
struct Action<STATION_INFORMATION>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, NavigationMessageData& data)
    {
        data.stationInformation = trim(std::string_view(input.begin(), input.end()));
    }
};

template<>
struct Action<LEAP_SECONDS>
{
    template<typename ParseInput>
    static void apply(const ParseInput& in, NavigationMessageData& data)
    {
        std::string_view str(in.begin(), in.end());
        auto toInt = [&str](int offset, int size) -> std::optional<int>
        {
            auto s = trim(str.substr(offset, size));
            return s.empty() ? std::optional<int> {} : stoi(s);
        };
        data.leapSeconds.currentNumber       = toInt(0, 6);             // I6
        data.leapSeconds.deltaTLSF           = toInt(6, 6);             // I6
        data.leapSeconds.wnLSF               = toInt(12, 6);            // I6
        data.leapSeconds.respectiveDayNumber = toInt(18, 6);            // I6
        auto sys                             = trim(str.substr(24, 3)); // A3
        if (sys.empty())
            data.leapSeconds.systemTimeIdentifier = SystemTimeIdentifier::GPS;
        else
            data.leapSeconds.systemTimeIdentifier = toSystemTimeIdentifier(sys);
    }
};

template<>
struct Action<NM_DATA>
{
    constexpr static int rowCount(SatelliteSystem sys)
    {
        switch (sys)
        {
        case SatelliteSystem::GPS:
        case SatelliteSystem::Galileo:
        case SatelliteSystem::QZSS:
        case SatelliteSystem::BDS:
        case SatelliteSystem::NavIC:
            return 7;
        case SatelliteSystem::GLONASS:
            return 4;
        case SatelliteSystem::SBASPayload:
            return 3;
        default:
            return 0;
        }
    }

    static std::optional<double> toDouble(std::string_view source)
    {
        auto s = trim(source);
        return s.empty() ? std::optional<double> {} : stod(s);
    }

    template<typename Rule,
             apply_mode A,
             rewind_mode M,
             template<typename...>
             class Action,
             template<typename...>
             class Control,
             typename ParseInput,
             typename... States>
    [[nodiscard]] static bool match(ParseInput& in, NavigationMessageData& data, States&&... st)
    {
        auto line = in.line_at(in.position());
        if (line.empty())
            return false;
        if (line.at(0) == ' ')
            return false;
        NMData d;
        d.satelliteSystem = toSatelliteSystem(line.substr(0, 1)); // A1
        d.id              = stoi(line.substr(1, 2));              // I2.2
        d.epoch.year      = stoi(line.substr(4, 4));              // 1X,I4
        d.epoch.month     = stoi(line.substr(9, 2));              // 1X,I2.2
        d.epoch.day       = stoi(line.substr(12, 2));             // 1X,I2.2
        d.epoch.hour      = stoi(line.substr(15, 2));             // 1X,I2.2
        d.epoch.minute    = stoi(line.substr(18, 2));             // 1X,I2.2
        d.epoch.second    = stoi(line.substr(21, 2));             // 1X,I2.2
        d.epoch.msec      = 0;
        auto offset       = 23;
        for (auto i : std::views::iota(0, 3)) // 3D19.12
        {
            d.datas.push_back(toDouble(line.substr(offset, 19)));
            offset += 19;
        }

        auto rc = rowCount(d.satelliteSystem);
        in.bump(line.size());
        consumeTail(in);
        for (auto i : std::views::iota(0, rc))
        {
            auto l = in.line_at(in.position());
            if (l.empty())
                return false;
            auto beforeSize = d.datas.size();
            if (l.at(0) == ' ')
            {
                for (auto view : l | std::views::drop(4) | std::views::chunk(19)) // 4X,4D19.12
                {
                    d.datas.push_back(toDouble(std::string_view(view.begin(), view.end())));
                }
                in.bump(l.size());
                consumeTail(in);
            }
            d.datas.resize(beforeSize + 4); // fill optional
        }
        data.datas.push_back(std::move(d));
        return true;
    }
};
} // namespace NavigationMessage
} // namespace PEG
} // namespace Rinex